#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2011 BidiX (http://bidix.info)
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License. entities
#
'''
	Data model module
	GAE Datastore is used for persistence of content
'''

import sys, datetime, time, logging, re
from google.appengine.ext import db
from google.appengine.api import users
from notewiki.ui import render_str, render_user
import xml.dom.minidom


def area_for(name):
	"""
		return the Area for this resource
	"""
	f = root_folder(name)
	if f == '_':
		return Area.shared
	if f == '__':
		return Area.sandbox
	if f and f[0] == '_':
		return Area.private
	return Area.public

def convert_unicode_keywords(kwds):
	'''
		See bug : Python does not accept unicode keywords <http://bugs.python.org/issue2646>
		Corrected in Python 2.6
		suppress this function in python 2.7
	'''
	new_kwds = {}
	for (key, value) in kwds.items():
		new_kwds[str(key)] = value
	return new_kwds
		

def tags_to_string(tags):
	first = True
	s = ''
	for tag in tags:
		if not first:
			s += ', '
		else:
			first = False
		s += tag
	return s

def string_to_tags(s):
	if not s:
		return []
	tags = s.split(',')
	tags = [tag.strip() for tag in tags] 
	return tags

def folders(name):
	if not name:
		return None
	f = name.split('/')
	folders = []
	url=""
	first = True
	for folder in f[0:len(f)-1]:
		if first:
			first = False
		else:
			url=url+"/"
		url = url+folder
		folders.append({'name':folder,'url':url})
	return folders

def root_folder(name):
	if name:	
		return name.split('/')[0].strip('/')
	else:
		return None
	
def basename(name):
	if name:
		f = filename(name).split('.')
		return f[0]
	else:
		return None

def dirname(name):
	if name:
		f = name.split('/')
		return '/'.join(f[0:len(f)-1])
	else:
		return None

def filename(name):
	if name:
		f = name.split('/')
		return f[len(f)-1]
	else:
		return None

def extension(name):
	f = name.split('.')
	if len(f) > 1:
		return f[len(f)-1]
	else:
		return None

def name_without_extension(name):
	if name:	
		return name.split('.')[0]
	else:
		return None


def parse_date(date_str):
	"""
	Parse a date exported from Datastore like : 2011-11-04 07:49:03.527283
	return a datetime.datetime
	"""
	date_lst = date_str.split(".")
	t = time.strptime(date_lst[0], "%Y-%m-%d %H:%M:%S")
	date= datetime.datetime(t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)
	ms= int(date_lst[1], 10)
	return date + datetime.timedelta(microseconds=ms)

def wikiname(user):
	"""
		return the user's wikiname
		wikiname is nickname where . are replaced by 
	"""
	return user.nickname().split('@')[0].lower().replace('.','_')
		

def user_role(name):
	"""
		return the user_role for this resource
	"""
	if users.is_current_user_admin():
		return Role.administrator
	user = users.get_current_user()
	area = area_for(name)
	if area == Area.sandbox:
		return Role.contributor
	if area == Area.shared:
		if user:
			return Role.reader
		else:
			return Role.forbidden
	elif area == Area.private:
		if user and (root_folder(name) == '_'+wikiname(user)):
			return Role.owner
		else:
			return Role.forbidden
	else:
		return Role.reader


class Entity(db.Model):
	'''
		Superclass of all entities
		Implements the import/export feature of all entities
	'''
	
	@classmethod
	def all_to_xml(cls):
		objects=cls.all().fetch(99999)
		xml='<?xml version="1.0" encoding="UTF-8"?>\n<notewiki>\n'
		for o in objects:
			xml = xml + o.to_xml()
		xml = xml + '</notewiki>'
		return xml

	@classmethod
	def set_from_xml(cls, xml_str):
		dom = xml.dom.minidom.parseString(xml_str)
		entity_list = dom.getElementsByTagName('entity')
		for entity in entity_list:
			if entity.attributes.has_key('kind'):
				cl = getattr(sys.modules[__name__], entity.attributes['kind'].nodeValue)
				obj = cl.from_xml(entity)
				obj.put()

class Article(Entity):
	'''
		Class for wiki node content
		Articles are organized in a tree structure
	'''
	#key_name always present : key().name()
	name = db.StringProperty(required=True)
	title = db.StringProperty()
	text = db.TextProperty()
	creator = db.UserProperty(auto_current_user_add=True)
	editor = db.UserProperty(auto_current_user=True)
	created = db.DateTimeProperty(required=True, auto_now_add=True)
	modified = db.DateTimeProperty(required=True, auto_now=True)
	tags = db.StringListProperty()
	folder = db.StringProperty()
	
	def __init__(self,
			   parent=None,
			   key_name=None,
			   _app=None,
			   _from_entity=False,
			   **kwds):
		'''
			name is Mandatory
			key_name must contain the same value as name'
			folder is set from name
			tags is computed from tag
		'''
		if key_name and not kwds.has_key('name'):
			kwds['name'] = key_name
		if kwds.has_key('name') and kwds['name']:
			key_name = kwds['name']
			pos = kwds['name'].rfind('/')
			if pos > -1:
				kwds['folder'] = kwds['name'][0:pos]
		if kwds.has_key('tag'):
			kwds['tags'] = string_to_tags(kwds['tag'])
		db.Model.__init__(self, key_name=key_name, **kwds)

	def add_child(self, child):
		self.children.append(child)
		self.put()

	@classmethod
	def add_or_replace(cls, **kwds):
		'''
			if entity exists preserve previous created attributes
			tags is computed from tag
		'''
		e = cls.get_by_name(kwds['name'])
		if kwds.has_key('tag'):
			kwds['tags'] = string_to_tags(kwds['tag'])
		if not e:
			e = cls(**convert_unicode_keywords(kwds))			
		else:
			for (key, value) in kwds.iteritems():
				if cls.properties().has_key(key) and (key != 'name') and (key != 'key_name'):
					setattr(e, key, value)
		e.put()
		return e
	
	@classmethod
	def create_folders(cls, name):
		if name:
			folder = Article.get_by_key_name(name)
			if not folder:
				folder = Article(key_name=name)
				folder.put()

	@classmethod
	def from_xml(cls, xml_node):
		properties = xml_node.getElementsByTagName('property')
		members={}
		members['tags'] =[]
		for property in properties:
			p_name = property.attributes['name'].nodeValue
			if len(property.childNodes) > 0:	
				value = property.childNodes[0].data
				if p_name in ['creator', 'editor']:
					members[p_name] = users.User(value)
				elif p_name in ['created', 'modified']:
					members[p_name] = parse_date(value)
				elif p_name == 'tags':
					members[p_name].append(value)
				else:
					members[p_name] = value
			else:
				if p_name == 'tags':
					members[p_name] = []
				else:	
					members[p_name] = None
		logging.debug("about to add_or_update Article %s"%members['name'])
		return cls.add_or_replace(**convert_unicode_keywords(members))

	@classmethod
	def get_by_name(cls,name):
		return cls.get_by_key_name(name)

	def get_parent(self):		
		query = db.GqlQuery("SELECT * FROM Folder " +
							"WHERE children = :1 " +
							" ORDER BY name",
							self.key())
		
		r = query.fetch(1)
		if r:
			return r[0]
		else:
			return None

	@classmethod
	def list_all(cls, folder=None):
		if folder and (folder != ' ') :
			query = db.GqlQuery("SELECT * FROM Article " +
							"WHERE folder = :1 " +
							" ORDER BY name",
							folder)
		else:
			query = db.GqlQuery("SELECT * FROM Article " +
							" ORDER BY name")
			
		r = query.fetch(9999)
		return r;

	@classmethod
	def list_all_visible(cls, user, folder=None):	
		article_list = cls.list_all(folder)
		visible_list = []
		for a in article_list:
			role = user_role(a.name)
			if role >= Role.reader:
				visible_list.append(a)
		return visible_list
	
	@classmethod
	def list_with_tag(cls, tag):
		query = db.GqlQuery("SELECT * FROM Article " +
							"WHERE tags = :1 " +
							" ORDER BY name",
							tag)
		r = query.fetch(9999)
		return r;
	
	@classmethod
	def list_with_regex(cls, user, exp):
		articles = cls.list_all_visible(user)
		match_list = []
		regex = re.compile(exp, re.IGNORECASE)
		for article in articles:
			if (article.title and regex.search(article.title)) or (article.text and regex.search(article.text)):
				match_list.append(article)
		return match_list
			

		
	def put(self):
		k = db.Model.put(self)
		self.create_folders(self.folder)
		return k

	def rename(self, new_name):
		new_article = Article(key_name=new_name, 
							name=new_name, title=self.title, 
							text=self.text, 
							tags=self.tags, 
							created=self.created, 
							creator=self.creator)
		new_article.put()
		self.delete()
		return new_article
	
	@classmethod
	def title_for_name(cls, name):
		article = cls.get_by_name(name)
		if article:
			return article.title
		else:
			return None
		
	def to_context(self, presentation=False):
		"""
			return context suitable to django template
		"""
		if self.title:
			title = self.title
		else:
			if self.name:
				title = ''
			else:
				title = '/'
		context =  { 
			'name': render_str(self.name),
			'title': render_str(title),
			'text': render_str(self.text),
			'creator': render_user(self.creator),
			'editor': render_user(self.editor),
			'created': self.created,
			'modified': self.modified,
			'tags': self.tags,
			'folders': folders(self.name),
			'filename': filename(self.name),
			'basename': basename(self.name),
			'dirname': dirname(self.name)
			}
		return context

	@classmethod
	def get_all_tags(cls, user):
		"""
			return an assoc tags contains in all articles associted with its use count 
		"""
		articles = cls.list_all_visible(user)
		tags = {}
		for article in articles:
			ts = article.tags
			for tag in ts:
				if not tags.has_key(tag):
					tags[tag] = 1
				else:
					tags[tag] += 1
		return tags

class Role:
	forbidden = 0
	reader = 1
	contributor = 2
	owner = 3
	administrator = 4	
		
class Area:
	unknown = 0
	public = 1
	shared = 2
	private = 3
	sandbox = 4
	